package cn.com.zero.web;

import cn.com.zero.base.exception.ErrorCodeException;
import cn.com.zero.base.vo.ResponseVO;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Objects;

/**
 * @author xiongxj
 * @version 1.0.0
 * @Description 统一处理控制层的异常，统一处理异常为 {@link ResponseVO} 结构
 * @createTime 2022/7/19 16:11
 */
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class ControllerExceptionHandlerAdvice {
    /**
     * 输出日志对象
     */
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 统一处理系统未处理到的异常
     *
     * @param exception 系统抛出的异常
     * @return 统一处理后的返回包
     */
    @ExceptionHandler(value = Throwable.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseVO handleException(Throwable exception) {
        logger.error("系统内部发生未处理异常：", exception);
        return ResponseVO.error(-99999, exception.getMessage());
    }

    /**
     * 统一异常捕获
     *
     * @param exception 异常对象
     * @return 统一处理后的返回包
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseVO responseNormalError(Exception exception) {
        logger.error("系统内部发生未知异常：{}", exception.getMessage(), exception);
        return ResponseVO.error(-99999);
    }

    /**
     * 统一处理系统运行时异常
     *
     * @param exception 异常信息
     * @return 统一处理后的返回包
     */
    @ExceptionHandler(value = RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseVO handleException(RuntimeException exception) {
        logger.warn("系统内部发生运行时异常：{}", exception.getMessage(), exception);
        return ResponseVO.error(-99999);
    }

    /**
     * 参数类型错误
     *
     * @param exception 异常信息
     * @return 统一处理后的返回包
     */
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO handle(MethodArgumentTypeMismatchException exception) {
        String stringBuilder = "参数[" +
                exception.getName() +
                "]类型错误，期望类型为：" +
                Objects.requireNonNull(exception.getRequiredType()).getSimpleName();
        return ResponseVO.error(-90001, stringBuilder);
    }

    /**
     * 缺少参数
     * 如：拦截 RequestParam 非空验证
     *
     * @param exception 异常信息
     * @return 统一处理后的返回包
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO handle(MissingServletRequestParameterException exception) {
        String stringBuilder = "缺少参数[" +
                exception.getParameterName() +
                "]，类型为：" +
                exception.getParameterType();
        return ResponseVO.error(-90001, stringBuilder);
    }

    /**
     * 参数校验错误
     * 方法参数 @Valid VO对象，而 VO对象 中的属性增加 @NotBlank @Min 等校验，
     * 当校验不通过时，抛出此异常
     *
     * @param exception 异常信息
     * @return 返回包
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO responseParamsError(MethodArgumentNotValidException exception) {
        return processParamsError(exception);
    }

    /**
     * 参数绑定错误
     *
     * @param exception 异常对象
     * @return 返回包
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO responseParamsError(BindException exception) {
        return processParamsError(exception);
    }

    /**
     * 统一处理请求参数校验(Validation验证 form 表单或?拼接参数验证不通过拦截)
     *
     * @param exception 异常对象
     * @return 返回包
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO responseParamsError(ConstraintViolationException exception) {
        if (exception.getConstraintViolations().size() > 0) {
            // 获取第一条错误内容，TODO 需要注意多个错误时返回顺序是随机的，如果要做到有序校验，需要通过定义 GroupSequence 实现。
            ConstraintViolation violation = exception.getConstraintViolations().iterator().next();
            if (NumberUtil.isNumber(violation.getMessage())) {
                return ResponseVO.error(Long.parseLong(violation.getMessage()));
            }
        }
        return ResponseVO.error(-90001);
    }

    /**
     * 处理请求json对象属性类型不匹配，json反序列化异常
     *
     * @param exception 异常信息
     * @return 返回包
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseVO validExceptionHandler(HttpMessageNotReadableException exception) {
        if (!(exception.getCause() instanceof InvalidFormatException)) {
            return ResponseVO.error(-90001, exception.getMessage());
        }

        InvalidFormatException invalidFormatException = (InvalidFormatException) exception.getCause();
        List<JsonMappingException.Reference> referenceList = invalidFormatException.getPath();
        if (referenceList == null || referenceList.isEmpty()) {
            return ResponseVO.error(-90001, exception.getMessage());
        }
        StringBuilder messageBuilder = new StringBuilder();
        for (JsonMappingException.Reference reference : referenceList) {
            if (reference == null || StrUtil.isBlank(reference.getFieldName())) {
                continue;
            }

            messageBuilder.append("参数[")
                    .append(reference.getFieldName())
                    .append("]类型错误；");
        }
        if (messageBuilder.length() > 1) {
            messageBuilder = new StringBuilder(messageBuilder.substring(0, messageBuilder.length() - 1));
        } else {
            messageBuilder.append(exception.getMessage());
        }

        return ResponseVO.error(-90001, messageBuilder.toString());
    }

    /**
     * 请求方法不支持，例如：提供的是post，但是客户端请求的是get
     *
     * @param exception 异常对象
     * @return 返回包
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseVO responseNotSupportedError(HttpRequestMethodNotSupportedException exception) {
        return ResponseVO.error(-90002);
    }

    /**
     * 错误返回码异常
     *
     * @param exception 异常对象
     * @return 返回包
     */
    @ExceptionHandler(ErrorCodeException.class)
    public ResponseVO responseCodeError(ErrorCodeException exception) {
        return ResponseVO.error(exception.getCode(), exception.getArgs());
    }

    /**
     * 统一处理参数错误
     *
     * @param result 参数绑定结果
     * @return 返回包
     */
    private ResponseVO processParamsError(BindingResult result) {
        StringBuilder messageBuilder = new StringBuilder("[");
        List<FieldError> fieldErrors = result.getFieldErrors();
        if (!CollectionUtils.isEmpty(fieldErrors)) {
            for (FieldError error : fieldErrors) {
                messageBuilder.append(error.getField()).append(error.getDefaultMessage()).append(";");
            }
            if (messageBuilder.length() > 1) {
                messageBuilder = new StringBuilder(messageBuilder.substring(0, messageBuilder.length() - 1));
            }
            messageBuilder.append("]");
        }
        return ResponseVO.error(-90001, messageBuilder.toString());
    }

}
